# coding=utf-8
import sys
import os
import json
import copy
from os.path import abspath, dirname

sys.path.insert(0, abspath(dirname(__file__)))
import tkinter
from tkinter import *
import Fun
import pack
import pyperclip
from tool import open_Files_window, is_sub_path, is_path_in_list, save_file_dialog
from tool import walk_file, copy_file
from loger_setup import logger as log
# *打包时添加
import requests
import tkinter.simpledialog
import aggdraw
import windnd
import pyperclip
from PIL import *
import win32gui
import win32print
import tkinter.filedialog
import lib_not_dr

uiName = "AutoPack"
ElementBGArray = {}
ElementBGArray_Resize = {}
ElementBGArray_IM = {}
WINDOW = Fun.GetElement(uiName, 'root')


# *指定项目信息
# 从配置文件读取参数
def load_config():
    config_file = os.path.join(os.path.dirname(__file__), 'config.json')
    default_config = {
        "PACKAGE_PATH": r'D:\Project\PyStand-py38-x64-ExcelTool',
        "APP_DIR": 'app',
        "SAVE_DIR_NAME": 'Save',
        "RESOURCES_DIR_NAME": 'Resources'
    }

    if os.path.exists(config_file):
        try:
            with open(config_file, 'r', encoding='utf-8') as f:
                config = json.load(f)
                # 合并默认配置和文件配置，确保所有键都存在
                for key, value in default_config.items():
                    if key not in config:
                        config[key] = value
                return config
        except Exception as e:
            log.error(f"读取配置文件失败: {e}")
            return default_config
    else:
        # 如果配置文件不存在，则创建默认配置文件
        with open(config_file, 'w', encoding='utf-8') as f:
            json.dump(default_config, f, indent=4, ensure_ascii=False)
        msg('已自动生成config.json文件')
        return default_config


# 加载配置，主要是用于“复制”时，各文件复制的位置的去向
config = load_config()
PACKAGE_PATH = config["PACKAGE_PATH"]  # 项目打包成的Pystand的文件夹路径，例如D:\Project\PyStand-py38-x64-ExcelTool
APP_DIR = config["APP_DIR"]  # PACKAGE_PATH中，用于存放项目生成的pyd，pyc文件的文件夹，例如app（对应D:\Project\PyStand-py38-x64-ExcelTool\app)
SAVE_DIR_NAME = config["SAVE_DIR_NAME"]  # PACKAGE_PATH中，用于存档的文件夹，例如Save（对应D:\Project\PyStand-py38-x64-ExcelTool\Save)
RESOURCES_DIR_NAME = config["RESOURCES_DIR_NAME"]  # PACKAGE_PATH中，用于存放资源的文件夹，例如Resources（对应D:\Project\PyStand-py38-x64-ExcelTool\Resources)

packer = pack.Pack()

def msg(text, title='提示'):
    Fun.MessageBox(text, title, 'info', WINDOW)


def echo_table(table):
    Fun.AddMultiRowText(uiName, 'table', 'end', table, ['normal'] * len(table))
    Fun.SetRowBGColor(uiName, 'table', 'all', '#AC92EC')


class UiData:
    msg_info = {
        'project_dir': '项目目录路径',
        'pyd_dir': 'pyd文件存储目录',
        'main_file_path': '项目入口文件'
    }
    default_json_path = 'option.json'

    def __init__(self):
        self.DATA = {
            'project_dir': '',
            'main_file_path': '',
            'pyd_dir': '',
            'recompile': False,
            'is_keep': False
        }
        self.init = True  # 确保界面刚展示，且没有配置文件时，不会报错
        # 打包配置
        self.use_multithreading = True  # 默认使用多线程打包
        self.max_workers = 3  # 默认最大并发数

    def check_params(self):
        return True

    def get_ui_data(self):
        """获取界面中，有用的值，便于功能函数获取参数"""
        for widget_name in self.DATA:
            if isinstance(self.DATA[widget_name], str):
                # 从控件中取文本
                self.DATA[widget_name] = Fun.GetText(uiName, widget_name)
            elif isinstance(self.DATA[widget_name], bool):
                # 从控件中取值
                self.DATA[widget_name] = Fun.GetCurrentValue(uiName, widget_name)
            else:
                raise ValueError('未找到合适的取值方式')
        # 参数的校验
        if self.check_params():  # 也可以在上面的for循环中校验
            return copy.deepcopy(self.DATA)
        else:
            return None

    def reset(self):
        """重置界面参数"""
        self.__init__()

    def save_ui_data(self, save_path):
        if not save_path:
            save_path = self.default_json_path
        data = self.get_ui_data()
        if data is None:
            msg('界面的设置有错误，无法保存')
            return None
        table = Fun.GetAllRowTextList(uiName, 'table')
        data['table'] = table
        with open(save_path, 'w', encoding='utf-8') as file:
            json.dump(data, file)
        return True

    def set_ui_data(self, data):
        if not data or not isinstance(data, dict):
            return None
        try:
            for widget_name in data:
                widget_value = data.get(widget_name, '')
                if isinstance(self.DATA.get(widget_name), str):
                    # 在控件中设置文本
                    Fun.SetText(uiName, widget_name, widget_value)
                elif isinstance(self.DATA.get(widget_name), bool):
                    # 在控件中设置值
                    Fun.SetCurrentValue(uiName, widget_name, widget_value)
                elif widget_name == 'table':
                    # 先清空表格中的所有数据
                    Fun.DeleteAllRows(uiName, 'table')
                    # 如果没有值，或者是二维的空列表，就不写入表格数据
                    if (not widget_value) or (widget_value and all(not sublist for sublist in widget_value)):
                        continue
                    echo_table(widget_value)
                else:
                    output('界面设置时，未找到合适的设置方式，因此不读取设置文件')
                    self.set_ui_data(self.DATA)
                    return False
            return True
        except Exception as e:
            output(f"对ui进行设置时出现错误: {e}")
            self.set_ui_data(self.DATA)
        return False

    def read_ui_data(self, save_path=None):
        if save_path is None:
            save_path = self.default_json_path
        if not os.path.exists(save_path):
            return None
        try:
            with open(save_path, 'r', encoding='utf-8') as file:
                data = json.load(file)
            return data
        except Exception as e:
            output(f"读取ui的设置文件时出现错误: {e}")
            return False


def output(text):
    Fun.InsertText(uiName, 'info_output', tkinter.END, f'{text}\n', 'normal')


uiData = UiData()


# ==================== 辅助函数 ====================
def check_default_path():
    root = Fun.GetText(uiName, 'project_dir')
    Result = Fun.CheckExist(root)
    if Result:
        return root
    else:
        return '.'


def update_ui_table():
    """删除部分文件后更新界面上的表格"""
    # 获取删除后的表格
    table = Fun.GetAllRowTextList(uiName, 'table')
    if not table:
        return
    for i, row in enumerate(table):
        # 每行的序号重新编号
        row[0] = str(i + 1)
    # table有内容时，展示内容
    if table and table != [[]]:
        # 清空表格
        Fun.DeleteAllRows(uiName, 'table')
        # 展示
        echo_table(table)


def check_module_name():
    py_paths = Fun.GetColumnTextList(uiName, 'table', 1)
    if not packer.check_module_names(py_paths):
        msg('表格内的py文件存在同名情况，请仔细检查是否有错误，也可以使用ctrl+F过滤掉所有同名文件，然后仅添加一个py文件')


def end_output():
    output('----------------')


# ==================== 文件处理相关函数 ====================
def prepare_and_generate_order(uiName):
    res = uiData.get_ui_data()
    if res is None:
        msg('输入的数据错误')
        return None

    py_paths = Fun.GetColumnTextList(uiName, 'table', 1)
    project_dir = res.get('project_dir')
    if not project_dir:
        msg(f'{project_dir}未找到')
        return None

    py_paths = [os.path.join(project_dir, path) for path in py_paths]
    order = packer.create_order(py_paths)
    order = order.strip()
    if order:
        output(f'生成了以下pyinstaller的打包命令，并已经复制到粘贴板\n{order}')
        pyperclip.copy(order)
        if packer.has_false:
            output(f'注意，在打包过程中出现过错误，请确认命令中每个pyd文件都已经存在')
    return order


def copy_resources(target_path=PACKAGE_PATH):
    project_dir = Fun.GetText(uiName, 'project_dir')
    save_path = os.path.join(project_dir, SAVE_DIR_NAME)
    resources_path = os.path.join(project_dir, RESOURCES_DIR_NAME)

    import shutil

    # 复制Save目录下的所有文件和非空目录到target_path\Save
    target_save_path = os.path.join(target_path, SAVE_DIR_NAME)
    if os.path.exists(save_path):
        try:
            # 创建目标目录
            os.makedirs(target_save_path, exist_ok=True)
            # 复制整个目录结构和文件
            for root, dirs, files in os.walk(save_path):
                # 计算相对路径
                rel_path = os.path.relpath(root, save_path)
                # 构建目标路径
                if rel_path == '.':
                    dst_root = target_save_path
                else:
                    dst_root = os.path.join(target_save_path, rel_path)
                    os.makedirs(dst_root, exist_ok=True)

                # 复制文件
                for file in files:
                    src_file = os.path.join(root, file)
                    dst_file = os.path.join(dst_root, file)
                    shutil.copy2(src_file, dst_file)

            output(f'已将 {save_path} 目录下的文件复制到 {target_save_path}')
        except Exception as e:
            output(f'复制Save目录时出错: {e}')
    else:
        output(f'Save目录不存在: {save_path}')

    # 复制resources目录下的文件到target_path\Resources（排除__pycache__目录和py文件）
    target_resources_path = os.path.join(target_path, RESOURCES_DIR_NAME)
    if os.path.exists(resources_path):
        try:
            # 创建目标目录
            os.makedirs(target_resources_path, exist_ok=True)
            # 复制文件，排除__pycache__目录和py文件
            for root, dirs, files in os.walk(resources_path):
                # 跳过__pycache__目录
                dirs[:] = [d for d in dirs if d != '__pycache__']

                # 计算相对路径
                rel_path = os.path.relpath(root, resources_path)
                # 构建目标路径
                if rel_path == '.':
                    dst_root = target_resources_path
                else:
                    dst_root = os.path.join(target_resources_path, rel_path)
                    os.makedirs(dst_root, exist_ok=True)

                # 复制非py文件
                for file in files:
                    if not file.endswith('.py'):  # 排除py文件
                        src_file = os.path.join(root, file)
                        dst_file = os.path.join(dst_root, file)
                        shutil.copy2(src_file, dst_file)

            output(
                f'已将 {resources_path} 目录下的非py文件复制到 {target_resources_path}')
        except Exception as e:
            output(f'复制{RESOURCES_DIR_NAME}目录时出错: {e}')
    else:
        output(f'{RESOURCES_DIR_NAME}目录不存在: {resources_path}')


# ==================== 数据处理函数 ====================
def refresh_table(uiName):
    """刷新表格状态，显示真实的pyd文件情况"""
    table = Fun.GetAllRowTextList(uiName, 'table')
    if table == [[]] or table is None:
        return

    # 获取界面参数
    res = uiData.get_ui_data()
    if res is None:
        return

    packer.deal_params(res)

    updated_count = 0
    for i, row in enumerate(table):
        try:
            # 获取py文件的完整路径
            py_rel_path = row[1]  # py文件的相对路径
            py_full_path = os.path.join(packer.project_dir, py_rel_path)

            # 检查py文件是否存在
            if not os.path.exists(py_full_path):
                new_status = 'py文件不存在'
            else:
                # 获取对应的pyd路径
                pyd_path = packer.py_full_path_to_pyd_full_path(py_full_path)
                backup_py_path = packer.py_full_path_to_pyd_py_path(py_full_path)

                # 判断真实状态
                pyd_exists = os.path.exists(pyd_path)
                backup_py_exists = os.path.exists(backup_py_path)

                if not pyd_exists:
                    new_status = '不存在'
                elif not backup_py_exists:
                    # pyd存在但备份py不存在，需要重新编译
                    new_status = '需要重新编译'
                else:
                    # 检查文件内容是否一致
                    try:
                        from tool import compare_files_by_hash
                        content_same = compare_files_by_hash(py_full_path, backup_py_path)
                    except Exception:
                        content_same = False

                    if not content_same:
                        # 文件内容不一致，需要重新编译
                        new_status = '需要重新编译'
                    else:
                        # pyd存在且文件内容一致，显示pyd相对路径
                        pyd_rel_path = os.path.relpath(pyd_path, packer.project_dir)
                        new_status = pyd_rel_path

            # 更新状态
            old_status = row[2]
            if old_status != new_status:
                row[2] = new_status
                updated_count += 1

        except Exception as e:
            row[2] = f'检查失败: {str(e)[:50]}'
            updated_count += 1

    # 更新表格显示
    if updated_count > 0:
        Fun.DeleteAllRows(uiName, 'table')
        echo_table(table)
        output(f'刷新完成，更新了 {updated_count} 个文件的状态')
    else:
        output('刷新完成，所有状态都是最新的')


# ==================== UI事件处理函数 ====================
# Button '项目目录浏览' 's Command Event :
def 项目目录浏览_onCommand(uiName, widgetName, threadings=0):
    openPath = Fun.SelectDirectory(title='打开目录查找', initDir=os.path.abspath('.'))
    if openPath:
        Fun.SetText(uiName, 'project_dir', openPath)
# Button '项目入口浏览' 's Command Event :
def 项目入口浏览_onCommand(uiName, widgetName, threadings=0):
    root = check_default_path()
    openPath = Fun.OpenFile("打开Python文件", [('Python File', '*.py'), ('All files', '*')], os.path.abspath(root))
    if openPath:
        Fun.SetText(uiName, 'main_file_path', openPath)
# Button 'pyd文件浏览' 's Command Event :
def pyd文件浏览_onCommand(uiName, widgetName, threadings=0):
    root = check_default_path()
    openPath = Fun.SelectDirectory(title='打开目录查找', initDir=os.path.abspath(root))
    if openPath:
        Fun.SetText(uiName, 'pyd_dir', openPath)
# Button '获取py' 's Command Event :
def 获取py_onCommand(uiName, widgetName, threadings=0):
    table = Fun.GetAllRowTextList(uiName, 'table')
    if table != []:
        choice = Fun.AskBox('请选择', '将要清除当前表格内容，重新获取py文件列表', WINDOW)
        if not choice:
            return
    # 清理表格中的所有行
    Fun.DeleteAllRows(uiName, 'table')
    # 获取界面参数
    res = uiData.get_ui_data()
    if res is None:
        msg('输入的数据错误')
        return
    # 用参数获取table的表格
    table = packer.get_table(res)
    if table and table != [[]]:
        # 显示表格
        echo_table(table)
        check_module_name()
# Button 'nuitka打包' 's Command Event :
def nuitka打包_onCommand(uiName, widgetName, threadings=1):
    res = uiData.get_ui_data()
    if res is None:
        msg('输入的数据错误')
        return
    refresh_table(uiName)
    table = Fun.GetAllRowTextList(uiName, 'table')
    if table == [[]]:
        msg('表格内无需要打包的py文件，请点击"获取py文件"按钮后再操作')
        return
    # 在编译开始前刷新表格状态，显示真实情况
    output('刷新表格状态，检查真实文件情况...')
    refresh_table(uiName)
    try:
        # 根据配置选择打包方式
        if uiData.use_multithreading:
            output(f'nuitka多线程打包pyd开始（最大并发数: {uiData.max_workers}）\n')
            # 设置打包器的最大并发数
            packer.max_workers = uiData.max_workers
            # 使用新的多线程打包方法
            packer.nuitka_pack_multithreaded(table, res)
        else:
            output('nuitka传统串行打包pyd开始\n')
            # 使用原有的串行打包方法
            packer.nuitka_pack(table, res)
        # 更新UI表格显示最新状态
        py_paths = Fun.GetColumnTextList(uiName, 'table', 1)
        project_dir = res.get('project_dir')
        py_paths = [os.path.join(project_dir, path) for path in py_paths]
        table = packer.py_full_paths_to_table(py_paths)
        if table:
            Fun.DeleteAllRows(uiName, 'table')
            echo_table(table)
        # 生成pyinstaller命令
        order = prepare_and_generate_order(uiName)
        if order:
            mode_text = '多线程' if uiData.use_multithreading else '串行'
            output(f'\nnuitka{mode_text}打包结束')
        else:
            output('\n打包完成，但未生成有效的pyinstaller命令')
    except Exception as e:
        output(f'打包过程中发生错误: {str(e)}')
        msg(f'打包失败: {str(e)}', '错误')
        # 记录详细错误信息供调试使用
        import traceback
        output(f'详细错误信息:\n{traceback.format_exc()}')
    finally:
        end_output()
# Button '生成打包命令' 's Command Event :
def 生成打包命令_onCommand(uiName, widgetName, threadings=0):
    prepare_and_generate_order(uiName)
    end_output()
# Button '打开pyd目录' 's Command Event :
def 打开pyd目录_onCommand(uiName, widgetName, threadings=0):
    text = Fun.GetText(uiName, 'pyd_dir')
    Result = Fun.CheckIsDir(text)
    if Result:
        Fun.StartUp(text)


# Button '保存设置' 's Command Event :
def 保存设置_onCommand(uiName, widgetName, threadings=0):
    res = uiData.get_ui_data()
    if not res:
        return
    # savePath = Fun.SaveFile("保存设置文件", [('Json File', '*.json')], os.path.abspath(r'.\option.json'), 'json')
    savePath = save_file_dialog()
    if not savePath:
        return
    save_ui_data(savePath)
# Button '保存设置' 's Double-Button-1 Event :
def 保存设置_onDoubleButton1(event, uiName, widgetName, threadings=0):
    res = uiData.get_ui_data()
    if not res:
        return
    save_ui_data(uiData.default_json_path)
# Button '保存设置' 's Button-3 Event :
def 保存设置_onButton3(event, uiName, widgetName, threadings=0):
    res = uiData.get_ui_data()
    if not res:
        return
    save_ui_data(uiData.default_json_path)
# ListView 'table's Button-3 Event :
def table_onButton3(event, uiName, widgetName, threadings=0):
    # Create the Popup Menu
    widget = Fun.GetElement(uiName, widgetName)
    PopupMenu = tkinter.Menu(widget, tearoff=False, font=('Microsoft YaHei', 18))
    PopupMenu.add_command(label="删除", accelerator="Ctrl+D", command=lambda: table_onButton3_Menu_删除(uiName, "删除"))
    PopupMenu.add_command(label="增加", accelerator="Ctrl+G", command=lambda: table_onButton3_Menu_增加(uiName, "增加"))
    PopupMenu.add_command(label="过滤", accelerator="Ctrl+F", command=lambda: table_onButton3_Menu_过滤(uiName, "过滤"))
    PopupMenu.post(event.x_root, event.y_root)
# EventMenu '删除' 's Command Event :
def table_onButton3_Menu_删除(uiName, itemName):
    rowIndex = Fun.GetSelectedRowIndex(uiName, 'table')
    Fun.DeleteRows(uiName, 'table', rowIndex)
    # rowIndex.sort(reverse=True)
    # for row in rowIndex:
    #     Fun.DeleteRow(uiName, 'table', row)
    update_ui_table()
    output(f'已删除{len(rowIndex)}个文件')
    end_output()


# EventMenu '增加' 's Command Event :
def table_onButton3_Menu_增加(uiName, itemName):
    res = uiData.get_ui_data()
    if not res:
        return
    file_type = 'Python Files (*.py)|*.py|'
    paths = open_Files_window('.', file_type=file_type, api_flag='多选')
    if not paths:
        return

    row_text_list = Fun.GetAllRowTextList(uiName, 'table')
    if not row_text_list:
        return
    index = len(row_text_list) + 1 if row_text_list != [[]] else 0
    exist_path_list = Fun.GetColumnTextList(uiName, 'table', 1)
    exist_path_list = [os.path.join(packer.project_dir, path) for path in exist_path_list]

    # *检测是否有重复
    path_list = [path for path in paths if not is_path_in_list(path, exist_path_list)]
    if len(paths) > len(path_list):
        output(f'检测到增加的文件中，部分已经在表格中存在，已忽略此部分重复文件')
    if not path_list:
        pass
    else:
        packer.deal_params(res)
        new_table = packer.add_py_path(path_list, index)
        echo_table(new_table)
        output(f'已增加{len(path_list)}个文件')
    end_output()
    check_module_name()


# EventMenu '过滤' 's Command Event :
def table_onButton3_Menu_过滤(uiName, itemName):
    name = Fun.InputBox('请输入要去除的文件名，包含的字符', None)
    if not name:
        msg('未输入任何字符，已取消')
        return
    c_text = Fun.GetColumnTextList(uiName, 'table', 1)
    if not c_text:
        return
    index_list = [c_text.index(item) for item in c_text if name in os.path.basename(item)]
    if not index_list:
        return
    info = ''
    for i in index_list:
        info += f'\n序号 {i + 1} 路径 {c_text[i]}'
    info = f'要删除的有以下选项：{info}'
    choice = Fun.AskBox('确认是否删除', info)
    if choice:
        Fun.DeleteRows(uiName, 'table', index_list)
        output(f'已删除以下路径：{info}')
        end_output()
    else:
        output('已取消')


# Form 'Form_1's Control-g Event :
def Form_1_onControl_g(event, uiName, widgetName, threadings=0):
    table_onButton3_Menu_增加(uiName, widgetName)
# Form 'Form_1's Control-d Event :
def Form_1_onControl_d(event, uiName, widgetName, threadings=0):
    table_onButton3_Menu_删除(uiName, widgetName)
# Form 'Form_1's Control-f Event :
def Form_1_onControl_f(event, uiName, widgetName, threadings=0):
    table_onButton3_Menu_过滤(uiName, widgetName)
# Form 'Form_1's Load Event :
def Form_1_onLoad(uiName, threadings=0):
    if os.path.exists(uiData.default_json_path):
        load_ui_data()
    uiData.init = False
# Button 'load_option' 's Command Event :
def load_option_onCommand(uiName, widgetName, threadings=0):
    path = Fun.OpenFile("读取json文件", [('Json File', '*.json')], os.path.abspath('.'))
    if path:
        load_ui_data(path)
# Button 'clean' 's Command Event :
def clean_onCommand(uiName, widgetName, threadings=1):
    data = uiData.get_ui_data()
    if data:
        packer.clean_artifacts(data)
    end_output()
# SwitchButton 'is_keep's Switch Event :
def is_keep_onSwitch(uiName, widgetName, value, threadings=0):
    value = Fun.GetCurrentValue(uiName, 'is_keep')
    if value:
        Fun.SetText(uiName, 'Label_5', 'pyd保持原目录')
    else:
        Fun.SetText(uiName, 'Label_5', 'pyd不保持原目录')
# SwitchButton 'recompile's Switch Event :
def recompile_onSwitch(uiName, widgetName, value, threadings=0):
    value = Fun.GetCurrentValue(uiName, 'recompile')
    if value:
        Fun.SetText(uiName, 'Label_6', '重新编绎pyd')
    else:
        Fun.SetText(uiName, 'Label_6', '不重新编绎pyd')
# Button '复制pyd目录' 's Command Event :
def 复制pyd目录_onCommand(uiName, widgetName, threadings=1):
    res = uiData.get_ui_data()
    if not res:
        return
    pyd_paths_text = Fun.GetColumnTextList(uiName, 'table', 2)
    if not pyd_paths_text or (len(set(pyd_paths_text)) == 1 and pyd_paths_text[0] == '不存在'):
        msg('表格中无可以复制的pyd文件')
        return
    path = PACKAGE_PATH
    if not path:
        path = '.'
    openPath = Fun.SelectDirectory(title='打开目录查找', initDir=os.path.abspath(path))
    if not openPath:
        return
    # 确保所有文件都复制到正确的位置
    target_app_dir = os.path.join(openPath, APP_DIR)
    target_resources_dir = os.path.join(openPath, RESOURCES_DIR_NAME)
    os.makedirs(target_app_dir, exist_ok=True)
    os.makedirs(target_resources_dir, exist_ok=True)
    packer.deal_params(res)
    # 获取Resources目录名称，用于后续区分
    resources_dir_name = RESOURCES_DIR_NAME
    # 分离需要复制到不同目录的pyd文件
    app_pyd_paths = []  # 需要复制到app目录的pyd文件
    resources_pyd_paths = []  # 需要复制到resources目录的pyd文件
    for pyd_path in pyd_paths_text:
        if pyd_path != '不存在':
            # 检查路径是否包含Resources目录
            path_parts = pyd_path.split(os.sep)
            if resources_dir_name in path_parts:
                resources_pyd_paths.append(pyd_path)
            else:
                app_pyd_paths.append(pyd_path)
    # 复制pyd文件到target_app_dir
    app_pyd_res = packer.copy_pyd_dir(app_pyd_paths, target_app_dir)
    # 复制Resources目录下的pyd文件到target_resources_dir
    resources_pyd_res = []
    project_dir = res.get('project_dir', '')
    for pyd_path in resources_pyd_paths:
        src = os.path.join(project_dir, pyd_path)
        if os.path.exists(src):
            try:
                # 计算相对于Resources目录的路径（排除pydFiles部分）
                # 查找Resources在路径中的位置
                path_parts = pyd_path.split(os.sep)
                if resources_dir_name in path_parts:
                    # 获取Resources之后的部分路径
                    resources_index = path_parts.index(resources_dir_name)
                    rel_path_parts = path_parts[resources_index + 1:]  # Resources之后的部分
                    rel_path = ""
                    if rel_path_parts:
                        rel_path = os.path.join(*rel_path_parts)
                        dst = os.path.join(str(target_resources_dir), str(rel_path))
                    else:
                        dst = str(target_resources_dir)
                    res_copy = copy_file(src, dst)
                    if res_copy is True:
                        # 构建相对于Resources目录的路径
                        resources_rel_path = ""
                        if rel_path_parts:
                            resources_rel_path = os.path.join(str(resources_dir_name), str(rel_path))
                        else:
                            resources_rel_path = str(resources_dir_name)
                        resources_pyd_res.append(resources_rel_path)
                    elif isinstance(res_copy, str):
                        output(res_copy)
            except Exception as e:
                output(f"复制Resources中的pyd文件 {pyd_path} 时出错: {e}")
    # 查找并复制pyc文件到相应目录
    app_pyc_res = []
    resources_pyc_res = []
    pyd_dir = res.get('pyd_dir', '')
    if pyd_dir and os.path.exists(pyd_dir):
        # 使用walk_file查找所有pyc文件
        pyc_files = walk_file(pyd_dir, ext='pyc', deep=True, get_full_paths=True)
        if pyc_files:
            for pyc_file in pyc_files:
                # 检查pyc文件是否在Resources目录下
                rel_path = os.path.relpath(pyc_file, pyd_dir)
                path_parts = rel_path.split(os.sep)
                try:
                    if resources_dir_name in path_parts:
                        # 复制到resources目录，保持原始结构
                        # 查找Resources在路径中的位置
                        resources_index = path_parts.index(resources_dir_name)
                        # 获取Resources之后的部分路径
                        rel_path_parts = path_parts[resources_index + 1:]  # Resources之后的部分
                        rel_path_in_resources = ""
                        if rel_path_parts:
                            rel_path_in_resources = os.path.join(*rel_path_parts)
                            dst = os.path.join(str(target_resources_dir), str(rel_path_in_resources))
                        else:
                            dst = str(target_resources_dir)
                        copy_result = copy_file(pyc_file, dst)
                        if copy_result is True:
                            # 构建相对于Resources目录的路径
                            resources_rel_path = ""
                            if rel_path_parts:
                                resources_rel_path = os.path.join(str(resources_dir_name), str(rel_path_in_resources))
                            else:
                                resources_rel_path = str(resources_dir_name)
                            resources_pyc_res.append(resources_rel_path)
                        elif isinstance(copy_result, str):
                            output(copy_result)
                    else:
                        # 复制到app目录
                        dst = os.path.join(str(target_app_dir), str(rel_path))
                        copy_result = copy_file(pyc_file, dst)
                        if copy_result is True:
                            app_pyc_res.append(str(rel_path))
                        elif isinstance(copy_result, str):
                            output(copy_result)
                except Exception as e:
                    output(f"复制pyc文件 {pyc_file} 时出错: {e}")
    # 显示结果
    total_copied = len(app_pyd_res) + len(resources_pyd_res) + len(app_pyc_res) + len(resources_pyc_res)
    if total_copied > 0:
        info = f'已复制完成{total_copied}个文件：\n'
        app_total = len(app_pyd_res) + len(app_pyc_res)
        if app_total > 0:
            info += f'\nAPP目录 ({target_app_dir}) 共 {app_total} 个文件:\n'
            if app_pyd_res:
                info += f'  PYD文件 {len(app_pyd_res)} 个:\n'
                # 修复类型错误：确保列表元素是字符串
                app_pyd_list = [str(item) for item in app_pyd_res[:5]]
                info += '  ' + '\n  '.join(app_pyd_list)  # 只显示前5个
                if len(app_pyd_res) > 5:
                    info += '\n  ...'
            if app_pyc_res:
                info += f'\n  PYC文件 {len(app_pyc_res)} 个:\n'
                # 修复类型错误：确保列表元素是字符串
                app_pyc_list = [str(item) for item in app_pyc_res[:5]]
                info += '  ' + '\n  '.join(app_pyc_list)  # 只显示前5个
                if len(app_pyc_res) > 5:
                    info += '\n  ...'
        resources_total = len(resources_pyd_res) + len(resources_pyc_res)
        if resources_total > 0:
            info += f'\nRESOURCES目录 ({target_resources_dir}) 共 {resources_total} 个文件:\n'
            if resources_pyd_res:
                info += f'  PYD文件 {len(resources_pyd_res)} 个:\n'
                # 修复类型错误：确保列表元素是字符串
                resources_pyd_list = [str(item) for item in resources_pyd_res[:5]]
                info += '  ' + '\n  '.join(resources_pyd_list)  # 只显示前5个
                if len(resources_pyd_res) > 5:
                    info += '\n  ...'
            if resources_pyc_res:
                info += f'\n  PYC文件 {len(resources_pyc_res)} 个:\n'
                # 修复类型错误：确保列表元素是字符串
                resources_pyc_list = [str(item) for item in resources_pyc_res[:5]]
                info += '  ' + '\n  '.join(resources_pyc_list)  # 只显示前5个
                if len(resources_pyc_res) > 5:
                    info += '\n  ...'
        output(info)
        end_output()
    else:
        pass
    # 复制资源文件
    copy_resources(PACKAGE_PATH)
    # 显示总结信息
    summary_info = f"""
==================================================
           本次复制操作完成总结
==================================================
总目标目录: {openPath}
文件统计:
  PYD文件总数: {len(app_pyd_res) + len(resources_pyd_res)} 个
    ├─ 资源目录(Resources): {len(resources_pyd_res)} 个
    └─ 应用目录(App): {len(app_pyd_res)} 个
  PYC文件总数: {len(app_pyc_res) + len(resources_pyc_res)} 个
    ├─ 资源目录(Resources): {len(resources_pyc_res)} 个
    └─ 应用目录(App): {len(app_pyc_res)} 个
额外操作:
  已将 Resources 目录内的非Python文件复制到目标 Resources 目录
  已将 Save 目录内的所有内容复制到目标 Save 目录
==================================================
"""
    output(summary_info)
def load_ui_data(save_path=uiData.default_json_path):
    ui_data = uiData.read_ui_data(save_path)
    uiData.set_ui_data(ui_data)
    ui_data = uiData.get_ui_data()
    if ui_data:
        packer.deal_params(ui_data)
        recompile_onSwitch(uiName, '', '', 0)
        is_keep_onSwitch(uiName, '', '', 0)
        output(f'已完成设置文件的读取：{os.path.abspath(save_path)}')
        end_output()
    else:
        output('读取设置文件时出现错误，请确认设置是否正确')
    return ui_data


def save_ui_data(savePath):
    res = uiData.save_ui_data(savePath)
    if res:
        if not savePath:
            savePath = uiData.default_json_path
        # output(f'已将当前设置保存在{os.path.abspath(uiData.json_path)}中')
        output(f'已将当前设置保存在{os.path.abspath(savePath)}中')
        end_output()
    else:
        output('保存设置文件失败：')


# 新增的打包配置管理函数
def 切换打包模式_onCommand(uiName, widgetName, threadings=0):
    """切换打包模式：多线程 vs 串行"""
    uiData.use_multithreading = not uiData.use_multithreading
    mode_text = '多线程' if uiData.use_multithreading else '串行'
    worker_info = f'（最大并发数: {uiData.max_workers}）' if uiData.use_multithreading else ''
    output(f'已切换到{mode_text}打包模式{worker_info}')
    end_output()


def 设置并发数_onCommand(uiName, widgetName, threadings=0):
    """设置最大并发数"""
    try:
        current_workers = uiData.max_workers
        new_workers = Fun.InputBox(f'请输入最大并发数（当前: {current_workers}）', str(current_workers))
        if new_workers:
            workers = int(new_workers)
            if 1 <= workers <= 10:  # 限制在合理范围内
                uiData.max_workers = workers
                output(f'已设置最大并发数为: {workers}')
            else:
                msg('并发数必须在 1-10 之间', '参数错误')
        end_output()
    except ValueError:
        msg('请输入有效的数字', '输入错误')
    except Exception as e:
        msg(f'设置失败: {str(e)}', '错误')


def 显示打包信息_onCommand(uiName, widgetName, threadings=0):
    """显示当前打包配置信息"""
    mode_text = '多线程' if uiData.use_multithreading else '串行'
    worker_info = f'最大并发数: {uiData.max_workers}' if uiData.use_multithreading else '无并发限制'

    info_text = f"""当前打包配置:
打包模式: {mode_text}
{worker_info}

优势对比:
多线程模式: 速度快，支持并行处理，适合多文件打包
串行模式: 稳定性高，内存占用少，适合单文件或调试时使用"""

    output(info_text)
    end_output()


# Button 'refresh' 's Command Event :
def refresh_onCommand(uiName, widgetName, threadings=0):
    refresh_table(uiName)
# Button '单个打包pyc' 's Command Event :
def 单个打包pyc_onCommand(uiName, widgetName, threadings=0):
    main_path = Fun.GetText(uiName, 'project_dir')
    if not Fun.CheckIsDir(main_path):
        msg('请选择正确的项目目录')
        return
    pyd_path = Fun.GetText(uiName, 'pyd_dir')
    if not Fun.CheckIsDir(pyd_path):
        msg('请选择正确的pyd目录')
        return
    filepath = Fun.OpenFile('请选择要打包的py文件', [('Python Files', '*.py')], main_path, True)
    if not filepath:
        return
    # 如果是多选文件，filepath是一个元组
    # 如果是单选文件，filepath是一个字符串
    file_list = filepath if isinstance(filepath, tuple) else (filepath,)
    import py_compile
    import os
    success_count = 0
    failed_files = []
    for py_file in file_list:
        try:
            # 获取py文件相对于项目目录的路径
            py_rel_path = os.path.relpath(py_file, main_path)
            py_name = os.path.splitext(py_rel_path)[0]
            pyc_name = os.path.splitext(py_rel_path)[0] + '.pyc'
            # 构建目标pyc文件的完整路径
            pyc_file = os.path.join(pyd_path, pyc_name)
            # 创建目标目录（如果不存在）
            pyc_dir = os.path.dirname(pyc_file)
            if not os.path.exists(pyc_dir):
                os.makedirs(pyc_dir)
            # 编译py文件为pyc文件
            py_compile.compile(py_file, cfile=pyc_file, doraise=True)
            success_count += 1
        except Exception as e:
            failed_files.append((py_file, str(e)))
    # 显示结果
    if success_count > 0:
        msg(f'成功编译 {success_count} 个文件到 {pyd_path} 目录')
    if failed_files:
        error_msg = f'编译失败 {len(failed_files)} 个文件:\n'
        for file, error in failed_files[:3]:  # 只显示前3个错误
            error_msg += f'{file}: {error}\n'
        if len(failed_files) > 3:
            error_msg += '...'
        msg(error_msg)
# Button '单个nuitka' 's Command Event :
def 单个nuitka_onCommand(uiName, widgetName, threadings=1):
    main_path = Fun.GetText(uiName, 'project_dir')
    if not Fun.CheckIsDir(main_path):
        msg('请选择正确的项目目录')
        return
    # 获取界面参数
    res = uiData.get_ui_data()
    if res is None:
        msg('输入的数据错误')
        return
    pyd_path = Fun.GetText(uiName, 'pyd_dir')
    if not Fun.CheckIsDir(pyd_path):
        msg('请选择正确的pyd目录')
        return
    filepath = Fun.OpenFile('请选择要打包的py文件', [('Python Files', '*.py')], main_path, True)
    if not filepath:
        return
    # 如果是多选文件，filepath是一个元组
    # 如果是单选文件，filepath是一个字符串
    file_list = filepath if isinstance(filepath, tuple) else (filepath,)
    # 设置packer参数
    packer.deal_params(res)
    success_count = 0
    failed_files = []
    for py_file in file_list:
        try:
            # 检查文件是否存在
            if not os.path.isfile(py_file):
                failed_files.append((py_file, '文件不存在'))
                continue
            # 使用packer的单文件打包方法
            output(f'开始打包 {py_file}...')
            packer.nuitka_one_file(py_file)
            success_count += 1
        except Exception as e:
            failed_files.append((py_file, str(e)))
    # 显示结果
    if success_count > 0:
        msg(f'成功打包 {success_count} 个文件')
    if failed_files:
        error_msg = f'打包失败 {len(failed_files)} 个文件:\n'
        for file, error in failed_files[:3]:  # 只显示前3个错误
            error_msg += f'{file}: {error}\n'
        if len(failed_files) > 3:
            error_msg += '...'
        msg(error_msg)
